﻿using System.Collections.Generic;
using Caliburn.Micro;
using NthDownload.Contracts;
using System.ComponentModel.Composition;
using NthDownload.Composition;
using System.Windows.Threading;
using System.Windows;

namespace NthDownload.ViewModels
{
    public class ShellViewModel : Conductor<object>.Collection.OneActive
    {
        #region Class Members

        private IViewModel _dialogItem;
        private IViewModel _menuItem;
        private VerticalAlignment _dialogVerticalAlignment;

        #endregion Class Members

        #region Constructor

        /// <summary>
        /// View model for the primary view
        /// </summary>
        public ShellViewModel()
        {
            //
            // Initialize local objects
            //
            NestedFrames = new Dictionary<IViewModel, DispatcherFrame>();

            //
            // Satisfy imports 
            //
            SatisfyImports();

            //
            // Subscribe to the navigation services events
            //
            NavigationService.ShowRequested += NavigationServiceShowRequested;
            NavigationService.ShowDialogRequested += NavigationServiceShowDialogRequested;

            //
            // Set Defaults
            //
            Dialogs = new List<IViewModel>();
            DialogItem = null;
            
            //
            // Start loading
            //
            LoadingViewModel loadingViewModel = new LoadingViewModel();
            NavigationService.Show(loadingViewModel);
            loadingViewModel.Load();
        }

        #endregion Constructor

        #region Properties

        /// <summary>
        /// The navigation service 
        /// </summary>
        [Import]
        public INavigationService NavigationService { get; set; }

        /// <summary>
        /// The navigation service
        /// </summary>
        [Import]
        public IEventAggregator EventAggregator { get; set; }

        /// <summary>
        /// The window manager
        /// </summary>
        [Import]
        public IWindowManager WindowManager { get; set; }

        /// <summary>
        /// Indicates if there are currently dialogs that are currently being managed
        /// </summary>
        public bool HasDialogs 
        {
            get
            {
                return Dialogs.Count > 0;
            }
        }

        /// <summary>
        /// The currently displayed dialog
        /// </summary>
        public IViewModel DialogItem
        {
            get
            {
                return _dialogItem;
            }
            set
            {
                _dialogItem = value;
                NotifyOfPropertyChange(() => DialogItem);
            }
        }

        /// <summary>
        /// The top menu
        /// </summary>
        public IViewModel MenuItem
        {
            get
            {
                return _menuItem;
            }
            set
            {
                _menuItem = value;
                NotifyOfPropertyChange(() => MenuItem);
            }
        }

        /// <summary>
        /// The vertical alignment to use when showing a dialog
        /// </summary>
        public VerticalAlignment DialogVerticalAlignment
        {
            get
            {
                return _dialogVerticalAlignment;
            }
            set
            {
                _dialogVerticalAlignment = value;
                NotifyOfPropertyChange(() => DialogVerticalAlignment);
            }
        }

        #endregion Properties

        #region Private Properties

        /// <summary>
        /// The dialogs that are currently being managed
        /// </summary>
        private List<IViewModel> Dialogs { get; set; }

        /// <summary>
        /// The dispatcher frames used when creating a nested message pumps to 
        /// block code execution on the maun UI thread
        /// </summary>
        private Dictionary<IViewModel, DispatcherFrame> NestedFrames { get; set; }

        #endregion Private Properties

        #region Private Methods

        /// <summary>
        /// Safely satisfies the imports for the current object. If there is an
        /// error during this process the application is shutdown.
        /// </summary>
        private void SatisfyImports()
        {
            if (!ImportService.TrySatisfyImports(this))
            {
                MessageBox.Show("Unable to satisfy imports on the Shell View. The application will now close.", "Imports", MessageBoxButton.OK, MessageBoxImage.Error);
                Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
                Application.Current.Shutdown();
            }
        }

        #endregion Private Methods

        #region Navigation Service Event Handlers

        /// <summary>
        /// Handles the show dialog event for the navigation service
        /// </summary>
        private void NavigationServiceShowDialogRequested(object sender, NavigationRequestedEventArgs e)
        {
            if (e.ViewModel == null) return;
            if (Dialogs.Contains(e.ViewModel)) return;

            //
            // Check that the imports of the view model have been satisfied
            //
            if (!e.ViewModel.ImportsSatisfied)
            {
                MessageBox.Show("Can't show the " + e.ViewModel.DisplayName + " view because it's imports haven't been satisfied.", "Imports", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            //
            // Set vertical alignment for the dialog
            //
            DialogVerticalAlignment = (!e.Fullscreen) ? VerticalAlignment.Center : VerticalAlignment.Stretch;

            //
            // Subscribe to the view models deactivated event
            //
            e.ViewModel.Deactivated += ViewModelDeactivated;

            //
            // Manually set the view models parent to the current conductor and activate it. This
            // allows the current conductor to handle the deactivation of the view even though 
            // it didn't activate the view model.
            //
            IChild child = e.ViewModel;
            child.Parent = this;

            IActivate activate = e.ViewModel;
            activate.Activate();

            //
            // Add view model to dialog collection and display
            //
            Dialogs.Add(e.ViewModel);
            DialogItem = e.ViewModel;

            //
            // Raise property changed event for the HasDialogs property
            //
            NotifyOfPropertyChange(() => HasDialogs);

            //
            // Invoke shown callback
            //
            if (e.ShownCallBack != null)
            {
                e.ShownCallBack.Invoke(e.ViewModel);
            }

            //
            // Created a nested message pump, this blocks code execution on the main Ui thread while
            // leaving the Ui respoinsive
            //
            DispatcherFrame nestedFrame = new DispatcherFrame(true);
            NestedFrames.Add(e.ViewModel, nestedFrame);
            Dispatcher.PushFrame(nestedFrame);
        }

        /// <summary>
        /// Handles the show event for the navigation service
        /// </summary>
        private void NavigationServiceShowRequested(object sender, NavigationRequestedEventArgs e)
        {
            if (e.ViewModel == null) return;

            //
            // Check that the imports of the view model have been satisfied
            //
            if (!e.ViewModel.ImportsSatisfied)
            {
                MessageBox.Show("Can't show the " + e.ViewModel.DisplayName + " view because it's imports haven't been satisfied.", "Imports", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            ActivateItem(e.ViewModel);

            //
            // Invoke shown callback
            //
            if (e.ShownCallBack != null)
            {
                e.ShownCallBack.Invoke(e.ViewModel);
            }
        }

        #endregion Navigation Service Event Handlers

        #region ViewModel Event Handlers

        /// <summary>
        /// Handles the deactivated event for view models
        /// </summary>
        private void ViewModelDeactivated(object sender, DeactivationEventArgs e)
        {
            IViewModel viewModel = sender as IViewModel;
            if (viewModel == null) return;

            //
            // Unsubscribe from the view models deactivated event
            //
            viewModel.Deactivated -= ViewModelDeactivated;

            //
            // Remove from dialog collection and show next catalogue
            //
            Dialogs.Remove(viewModel);
            DialogItem = (Dialogs.Count > 0) ? Dialogs[Dialogs.Count - 1] : null;

            //
            // Raise property changed event for the HasDialogs property
            //
            NotifyOfPropertyChange(() => HasDialogs);

            //
            // Instruct nested frame to continue
            //
            DispatcherFrame nestedFrame;
            if (NestedFrames.TryGetValue(viewModel, out nestedFrame))
            {
                nestedFrame.Continue = false;
            }
            else
            {
                MessageBox.Show("Unable to find nested message frame.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }

        #endregion ViewModel Event Handlers
    }
}
